/*
 *    Copyright 2008-2009 Simone Tripodi
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.testnguice.providers.xml;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.text.FieldPosition;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.digester.Digester;
import org.testng.annotations.DataProvider;
import org.xml.sax.SAXException;

/**
 * A data provider that reads test arguments from a basic XML document.
 *
 * @author Simone Tripodi
 * @version $Id: XMLDataProvider.java 151 2009-11-24 08:24:58Z simone.tripodi $
 */
public final class XMLDataProvider {

    /**
     * The {@link DataProvider} method's id used in this class.
     * Useful when declaring the {@link DataProvider} to refer the name.
     */
    public static final String DATA_PROVIDER_ID = "xml-provider";

    /**
     * The XML classpath resource pattern.
     */
    private static final MessageFormat CLASSPATH_RESOURCE_PATTERN = new MessageFormat("{0}.{1}.test-data.xml");

    /**
     * The default field position.
     */
    private static final FieldPosition DEFAULT_FIELD_POSITION = new FieldPosition(0);

    /**
     * Builds a data provider starting from a {@link Method}, that provides
     * useful informations such its arguments types, used to bind each XML line
     * to proper types.
     *
     * @param method the method under test
     * @return an {@link Iterator} of method's arguments
     */
    @DataProvider(name = DATA_PROVIDER_ID)
    @SuppressWarnings("unchecked")
    public static Iterator<Object[]> getDataProvider(final Method method) {
        String className = method.getDeclaringClass().getSimpleName();

        StringBuffer result = new StringBuffer();
        Object[] args = new Object[] { className, method.getName() };
        CLASSPATH_RESOURCE_PATTERN.format(args, result, DEFAULT_FIELD_POSITION);
        String classPathResource = result.toString();
        String debugClassPathResource = method
                                        .getDeclaringClass()
                                        .getPackage()
                                        .getName()
                                        .replace('.', '/')
                                        + '/'
                                        + classPathResource;

        InputStream inputStream = method.getDeclaringClass().getResourceAsStream(classPathResource);

        if (inputStream == null) {
            throw new RuntimeException("Impossible to locate resource '"
                    + classPathResource
                    + "', please be sure is in the classpath");
        }

        Class<?>[] types = method.getParameterTypes();
        ObjectArraySetArgumentRule argumentRule = new ObjectArraySetArgumentRule(types);

        Digester digester = new Digester();
        digester.setNamespaceAware(true);
        digester.addObjectCreate("xml-dataprovider", ArrayList.class);
        digester.addRule("xml-dataprovider/test", new ObjectArrayCreateRule(types.length, argumentRule));
        digester.addRule("xml-dataprovider/test/argument", argumentRule);
        digester.addSetNext("xml-dataprovider/test", "add");

        try {
            return ((List<Object[]>) digester.parse(inputStream)).iterator();
        } catch (SAXException e) {
            throw new RuntimeException("An error occurred while parsing resource '"
                    + debugClassPathResource
                    + "', see nested exceptions", e);
        } catch (IOException e) {
            throw new RuntimeException("An error occurred while reading resource '"
                    + debugClassPathResource
                    + "', see nested exceptions", e);
        }
    }

}
